import numpy as np
import matplotlib.pyplot as plt
import copy


class GA:
    """
    SA算法框架
    """
    def __init__(self,N=4,precison=5,mutation_rate=0.1) -> None:
        self.N=N
        self.data=None
        self.best_value=1000
        self.precision=precison
        self.mutation_rate=mutation_rate
        self.population={}
        self.flag=1


    def _encoding(self,x):
        x=float(x)
        row=bin(int(x)).replace('0b','')
        l=len(row)

        if l<=self.precision:
            res='0'*(self.precision-l)+row
            return res

        else:
            raise ValueError


    def _decoding(self,bin_x):
        return str(int(bin_x,2))

    def _crossover(self,gen_1:str,gen_2):
        """
        均匀交叉
        """
        x1=list("0"*self.precision)
        x2=list("0"*self.precision)
        for i ,item in enumerate(gen_1):
            if item==gen_2[i]:
                x1[i]=item
                x2[i]=item
            else:
                p0=np.random.rand()
                if p0<=0.5:
                    posi=1
                else:
                    posi=0
                
                x1[i]=str(posi)
                x2[i]=str(1-posi)

        return "".join(x1),"".join(x2)

    def _cross_over(self,gen_1:str,gen_2:str):
        posi1_1=gen_2[:2]
        posi2_1=gen_1[:2]
        posi1_2=gen_1.replace(posi1_1[0],'').replace(posi1_1[1],'')
        posi2_2=gen_2.replace(posi2_1[0],'').replace(posi2_1[1],'')

        return [posi1_1+posi1_2,posi2_1+posi2_2]

    def _mutation_method(self,xs):
        res=[]
        for it in xs:
            p0=np.random.rand()
            if p0<=self.mutation_rate:
                item=list(it)
                l=list(range(len(it)))
                posi=[]
                for _ in range(2):
                    posi1=np.random.choice(l)
                    posi.append(posi1)
                    l.remove(posi1)
                
                item[posi[0]],item[posi[1]]=item[posi[1]],item[posi[0]]
                res.append("".join(item))
            else:
                res.append(it)

        return res
                


    def _mutation(self,xs):
        """
        变异
        """
        x_new=[list(xs[0]),list(xs[1])]
        for k,it in enumerate(xs):
            p0=np.random.randn(self.precision)
            for i,p in enumerate(p0):
                if p<=self.mutation_rate:
                    x_new[k][i]=str(1-int(it[i]))


        x_res=[]
        for it in x_new:
            x_res.append("".join(it))
        return x_res

    def get_value(self,x):
        
        init_length=0
        xs=list(x)
        value=0

        for it in xs:
            workpiece=self.data[it]
            init_length+=workpiece[0]
            

            t=workpiece[1]-init_length

            if t>0:
                value+=t*workpiece[2]

            else:
                value+=(-t*workpiece[3])
        return value

    def get_samples(self,population):
        population_copy= list(population.keys())
        xs=[]
        x1=np.random.choice(population_copy)
        # print(x1)
        xs.append(x1)
        population_copy.remove(x1)
        x2=np.random.choice(population_copy)
        xs.append(x2)

        return xs

    def _initiate_population(self,xs):
        res={}
        
        if self.flag==1:
            
            for i in range(self.N):
                last = list(xs)
                last[i], last[i+1] = last[i+1], last[i]

                x="".join(last)
                res[x]=self.get_value(x)
        else:
            xs_new=copy.deepcopy(xs)
            for _ in range(self.N):
                x=np.random.choice(xs_new)
                res[x]=self.get_value(x)
                xs_new.remove(x)

        return res

    def _seed_children_flag(self,xs):
        for it in xs:
            x_key=self._decoding(it)
            if x_key not in self.all_population:
                return False

        else:
            return True

    def _update_population(self,xs):

        population_now=self.population
        for it in xs:
            # x1=self._decoding(it)
            population_now[it]=self.get_value(it)

        row=sorted(population_now.items(),key=lambda item:item[1])[:self.N]
        
        # x2=self._decoding(xs[1])
        row_dict=dict(row)
        
        # row_dict[x1]=self.get_value(x1)
        # row_dict[x2]=self.get_value(x2)
        return row_dict

    def __call__(self,data,epoch=100):
        self.data=data
        self.all_population=list(self.data.keys())
        self.population=self._initiate_population("".join(self.all_population))
        self.best_value=min(list(self.population.values()))

        
        xs=self.get_samples(self.population)
        # xs_bin=[self._encoding(x) for x in xs]

        for i in range(epoch):
            # while True:
            xs_row=self._cross_over(xs[0],xs[1])
            xs_mutation=self._mutation_method(xs_row)
                
                # flag=self._seed_children_flag(xs_mutation)
                # if flag:
                #     break
        

            self.population=self._update_population(xs=xs_mutation)

            self.best_value=min(list(self.population.values()))
            xs=self.get_samples(self.population)


        print(self.best_value)
            



        






if __name__=="__main__":
    model=GA(N=4)
    data={"1":[10,15,3,10],"2":[8,20,2,22],"3":[6,10,5,10],"4":[7,30,4,8],"5":[4,12,6,15]}
    model(data=data)